<?php

class DevelTypo {

    /**
     * Типографит текст.
     * @param String $str
     * @return String
     */
    public static function processText($str) { return self::getInstance()->doProcessText($str); }

    /**
     * Типографит текст, не задевая при этом тэги HTML.
     * @param String $str
     * @return String
     */
    public static function processHtml($str) { return self::getInstance()->doProcessHtml($str); }

    /**
     * @var ITypoStrategy
     */
    private $oStrategy;

    private function __construct(ITypoStrategy $oStrategy) { $this->oStrategy = $oStrategy; }

    /**
     * @return DevelTypo
     */
    private static function getInstance() {

        static $instance = null;
        if (!$instance) $instance = new self(new DevelTypoStrategy());

        return $instance;
    }

    private function doProcessText($str) {

        if (!trim($str)) return '';

        $str = ' ' . $str . ' ';

        foreach ($this->oStrategy->getProcessRegexRules() as $rule) {
            list($arrFrom, $arrTo) = $rule;
            $str = preg_replace($arrFrom, $arrTo, $str);
        }

        return trim($str);
    }


    private function doProcessHtml($str) {

        $oProtector = new TextBlockProtector();

        $str = $oProtector->protectText($str, $this->oStrategy->getSafeBlocksRegex());
        $str = $this->processText($str);
        $str = $oProtector->unprotectText($str);

        return $str;
    }
}

class TextBlockProtector {

    private $hStorage;

    public function protectText($text, $regex) {
        $this->hStorage = array();
        return preg_replace_callback($regex, array($this, 'add'), $text);
    }

    public function unprotectText($text) {
        return strtr($text, $this->hStorage);
    }

    private function add(array $arrMatches) {
        $key = '<tag' . count($this->hStorage) . '>';
        $this->hStorage[$key] = $arrMatches[0];
        return $key;
    }
}